
package com.haiyou.common.binary.writer;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang3.Validate;

import com.haiyou.common.util.collect.CollectionUtils;
import com.haiyou.common.util.object.ClassUtils;

import lombok.extern.slf4j.Slf4j;

/**
 * 
 * 
 * @Description: 二进制写入工具
 * @author xingyuan
 * @date 2022年2月15日 下午5:05:33
 */
@Slf4j
public class BinaryWriter implements Writer {

	private ByteBuffer buf;

	public BinaryWriter() {

	}

	@Override
	public byte[] array() {

		try {
			byte[] array = this.buf.array();
			log.debug("buf.array():" + array.length + "," + Arrays.toString(array));
			return array;
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	}

	@Override
	public ByteBuffer buffer() {

		return this.buf;
	}

	@Override
	public Writer writeBuf(ByteBuffer buffer) {

		if (this.buf == null) {
			this.buf = buffer;
		} else {
			this.buf.flip();
			buffer.flip();
			this.buf = ByteBuffer.allocate(this.buf.capacity() + buffer.capacity()).put(this.buf).put(buffer);
		}
		return this;
	}

	@Override
	public Writer writeByte(int v) {

		writeBuf(ByteBuffer.allocate(1).put((byte) v));
		return this;
	}

	@Override
	public Writer writeShort(int v) {

		writeBuf(ByteBuffer.allocate(2).putShort((short) v));
		return this;
	}

	@Override
	public Writer writeInt(int v) {

		writeBuf(ByteBuffer.allocate(4).putInt(v));
		return this;
	}

	@Override
	public Writer writeLong(long v) {

		writeBuf(ByteBuffer.allocate(8).putLong(v));
		return this;
	}

	@Override
	public Writer writeFloat(float v) {

		writeInt(Float.floatToIntBits(v));
		return this;
	}

	@Override
	public Writer writeDouble(double v) {

		writeLong(Double.doubleToLongBits(v));
		return this;
	}

	@Override
	public Writer writeBool(boolean v) {

		byte b = (byte) (v ? 1 : 0);
		writeByte(b);
		return this;
	}

	@Override
	public Writer writeChar(char v) {

		writeBuf(ByteBuffer.allocate(2).putChar(v));
		return this;
	}

	@Override
	public Writer writeString(String v, Charset charset) {

		byte[] bs = null;
		try {
			bs = string2Bytes(v, charset);
		} catch (Exception e) {
			throw new RuntimeException(v, e);
		}
		ByteBuffer buffer = ByteBuffer.allocate(bs.length + 2);
		buffer.putShort((short) bs.length);
		buffer.put(bs);
		writeBuf(buffer);
		return this;
	}

	@Override
	public <K, V> Writer writeMap(Map<K, V> map) {

		writeShort(map.size());
		for (Entry<K, V> entry : map.entrySet()) {
			K k = entry.getKey();
			V v = entry.getValue();
			Class<K> keyCls = (Class<K>) k.getClass();
			Class<V> valueCls = (Class<V>) v.getClass();
			if (keyCls == byte.class || keyCls == Byte.class) {
				writeByte((Byte) k);
			} else if (keyCls == short.class || keyCls == Short.class) {
				writeShort((Short) k);
			} else if (keyCls == int.class || keyCls == Integer.class) {
				writeInt((Integer) k);
			} else if (keyCls == long.class || keyCls == Long.class) {
				writeLong((Long) k);
			} else if (keyCls == float.class || keyCls == Float.class) {
				writeFloat((Float) k);
			} else if (keyCls == double.class || keyCls == Double.class) {
				writeDouble((Double) k);
			} else if (keyCls == boolean.class || keyCls == Boolean.class) {
				writeBool((Boolean) k);
			} else if (keyCls == char.class || keyCls == Character.class) {
				writeChar((Character) k);
			} else if (keyCls == String.class) {
				writeUTF8((String) k);
			} else if (ClassUtils.isAssignableFrom(Writable.class, keyCls)) {
				writeObject((Writable) k);
			}
			if (valueCls == byte.class || valueCls == Byte.class) {
				writeByte((Byte) v);
			} else if (valueCls == short.class || valueCls == Short.class) {
				writeShort((Short) v);
			} else if (valueCls == int.class || valueCls == Integer.class) {
				writeInt((Integer) v);
			} else if (valueCls == long.class || valueCls == Long.class) {
				writeLong((Long) v);
			} else if (valueCls == float.class || valueCls == Float.class) {
				writeFloat((Float) v);
			} else if (valueCls == double.class || valueCls == Double.class) {
				writeDouble((Double) v);
			} else if (valueCls == boolean.class || valueCls == Boolean.class) {
				writeBool((Boolean) v);
			} else if (valueCls == char.class || valueCls == Character.class) {
				writeChar((Character) v);
			} else if (valueCls == String.class) {
				writeUTF8((String) v);
			} else if (ClassUtils.isAssignableFrom(Writable.class, valueCls)) {
				writeObject((Writable) v);
			}
		}
		return this;
	}

	@Override
	public <E> Writer writeCollection(Collection<E> c, Class<E> cls) {

		if (CollectionUtils.isEmpty(c)) {
			writeShort((short) 0);
		} else {
			writeShort((short) c.size());
			if (cls == byte.class || cls == Byte.class) {
				for (E b : c) {
					writeByte((Byte) b);
				}
			} else if (cls == short.class || cls == Short.class) {
				for (E b : c) {
					writeShort((Short) b);
				}
			} else if (cls == int.class || cls == Integer.class) {
				for (E b : c) {
					writeInt((Integer) b);
				}
			} else if (cls == long.class || cls == Long.class) {
				for (E b : c) {
					writeLong((Long) b);
				}
			} else if (cls == float.class || cls == Float.class) {
				for (E b : c) {
					writeFloat((Float) b);
				}
			} else if (cls == double.class || cls == Double.class) {
				for (E b : c) {
					writeDouble((Double) b);
				}
			} else if (cls == boolean.class || cls == Boolean.class) {
				for (E b : c) {
					writeBool((Boolean) b);
				}
			} else if (cls == char.class || cls == Character.class) {
				for (E b : c) {
					writeChar((Character) b);
				}
			} else if (cls == String.class) {
				for (E b : c) {
					writeUTF8((String) b);
				}
			} else if (ClassUtils.isAssignableFrom(Writable.class, cls)) {
				for (E b : c) {
					writeObject((Writable) b);
				}
			}
		}
		return this;
	}

	@Override
	public <E> Writer writeObject(E obj) {
		if (obj == null) {
			writeShort((short) 0);
			return this;
		}
		Validate.isTrue(obj instanceof Writable);
		Writer writer = new BinaryWriter();
		((Writable) obj).write(writer);
		writeBytes(writer.array());
		return this;
	}

	@Override
	public Writer writeBytes(byte[] v) {

		writeShort(v.length);
		for (int i = 0; i < v.length; i++) {
			writeByte(v[i]);
		}
		return this;
	}

	@Override
	public Writer writeShorts(short[] v) {

		writeShort(v.length);
		for (int i = 0; i < v.length; i++) {
			writeShort(v[i]);
		}
		return this;
	}

	@Override
	public Writer writeInts(int[] v) {

		writeShort(v.length);
		for (int i = 0; i < v.length; i++) {
			writeInt(v[i]);
		}
		return this;
	}

	@Override
	public Writer writeLongs(long[] v) {

		writeShort(v.length);
		for (int i = 0; i < v.length; i++) {
			writeLong(v[i]);
		}
		return this;
	}

	@Override
	public Writer writeFloats(float[] v) {

		writeShort(v.length);
		for (int i = 0; i < v.length; i++) {
			writeFloat(v[i]);
		}
		return this;
	}

	@Override
	public Writer writeDoubles(double[] v) {

		writeShort(v.length);
		for (int i = 0; i < v.length; i++) {
			writeDouble(v[i]);
		}
		return this;
	}

	@Override
	public Writer writeBooleans(boolean[] v) {

		writeShort(v.length);
		for (int i = 0; i < v.length; i++) {
			writeBool(v[i]);
		}
		return this;
	}

	@Override
	public Writer writeChars(char[] v) {

		writeShort(v.length);
		for (int i = 0; i < v.length; i++) {
			writeChar(v[i]);
		}
		return this;
	}

	@Override
	public Writer writeStrings(String[] v) {

		writeShort(v.length);
		for (int i = 0; i < v.length; i++) {
			writeUTF8(v[i]);
		}
		return this;
	}

	@Override
	public Writer writeDate(Date date) {

		writeLong(date == null ? 0 : date.getTime());
		return this;
	}

	@Override
	public Writer writeEnum(Enum e) {

		writeUTF8(e != null ? e.name() : null);
		return this;
	}

	@Override
	public Writer writeClazz(Class<?> clazz) {

		writeUTF8(clazz != null ? clazz.getName() : null);
		return this;
	}

	@Override
	public Writer write$Bytes(Byte[] v) {

		byte[] arr = new byte[v.length];
		for (int i = 0; i < arr.length; i++) {
			arr[i] = v[i];
		}
		writeBytes(arr);
		return this;
	}

	@Override
	public Writer write$Shorts(Short[] v) {

		short[] arr = new short[v.length];
		for (int i = 0; i < arr.length; i++) {
			arr[i] = v[i];
		}
		writeShorts(arr);
		return this;
	}

	@Override
	public Writer write$Integers(Integer[] v) {

		int[] arr = new int[v.length];
		for (int i = 0; i < arr.length; i++) {
			arr[i] = v[i];
		}
		writeInts(arr);
		return this;
	}

	@Override
	public Writer write$Longs(Long[] v) {

		long[] arr = new long[v.length];
		for (int i = 0; i < arr.length; i++) {
			arr[i] = v[i];
		}
		writeLongs(arr);
		return this;
	}

	@Override
	public Writer write$Floats(Float[] v) {

		float[] arr = new float[v.length];
		for (int i = 0; i < arr.length; i++) {
			arr[i] = v[i];
		}
		writeFloats(arr);
		return this;
	}

	@Override
	public Writer write$Doubles(Double[] v) {

		double[] arr = new double[v.length];
		for (int i = 0; i < arr.length; i++) {
			arr[i] = v[i];
		}
		writeDoubles(arr);
		return this;
	}

	@Override
	public Writer write$Booleans(Boolean[] v) {

		boolean[] arr = new boolean[v.length];
		for (int i = 0; i < arr.length; i++) {
			arr[i] = v[i];
		}
		writeBooleans(arr);
		return this;
	}

	@Override
	public Writer write$Chars(Character[] v) {

		char[] arr = new char[v.length];
		for (int i = 0; i < arr.length; i++) {
			arr[i] = v[i];
		}
		writeChars(arr);
		return this;
	}
}
